package com.qsmx.store.app.main;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.Spinner;

import androidx.annotation.Nullable;
import androidx.core.app.PendingIntentCompat;
import androidx.core.content.ContextCompat;

import com.acs.smartcard.Features;
import com.acs.smartcard.PinProperties;
import com.acs.smartcard.Reader;
import com.acs.smartcard.TlvProperties;
import com.qsmx.store.app.R;
import com.qsmx.store.app.databinding.DelegateNfcTestBinding;
import com.qsmx.store.app.util.Hex;
import com.qsmx.store.app.util.UsbReader;
import com.qsmx.store.core.delegates.AtmosDelegate;

public class NfcReadTestDelegate extends AtmosDelegate {

    private DelegateNfcTestBinding mBinding;
    private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
    private UsbManager mManager;

    private Reader mReader;
    private PendingIntent mPermissionIntent;

    @Override
    public Object setLayout() {
        return R.layout.delegate_nfc_test;
    }

    @Override
    public void onBindView(@Nullable Bundle savedInstanceState, View rootView) {
        mBinding = DelegateNfcTestBinding.bind(rootView);
        // Get USB manager
        mManager = UsbReader.getInstance(getContext()).getManager();
        mReader = UsbReader.getInstance(getContext()).getReader();

        int flags = PendingIntent.FLAG_UPDATE_CURRENT;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            flags |= PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT;
        }
        mPermissionIntent = PendingIntentCompat.getBroadcast(getContext(), 0,
                new Intent(ACTION_USB_PERMISSION), flags, true);
        IntentFilter filter = new IntentFilter();
        filter.addAction(ACTION_USB_PERMISSION);
        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
        ContextCompat.registerReceiver(getContext(), mReceiver, filter, ContextCompat.RECEIVER_EXPORTED);
        String deviceName = "";
        for (UsbDevice device : mManager.getDeviceList().values()) {
            if (mReader.isSupported(device)) {
                deviceName = device.getDeviceName();
            }
        }
        // For each device
        for (UsbDevice device : mManager.getDeviceList().values()) {
            // If device name is found
            if (deviceName.equals(device.getDeviceName())) {
                // Request permission
                mManager.requestPermission(device,
                        mPermissionIntent);
                break;
            }
        }
    }

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {

        public void onReceive(Context context, Intent intent) {

            String action = intent.getAction();

            if (ACTION_USB_PERMISSION.equals(action)) {

                synchronized (this) {

                    UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                    if (intent.getBooleanExtra(
                            UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                        if (device != null) {
                            new Thread(new OpenTask(device)).start();
                        }
                    }
                }

            } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {

            }
        }
    };


    private static class PowerParams {

        public int slotNum;
        public int action;
    }


    private class OpenTask implements Runnable {

        private final UsbDevice mDevice;

        public OpenTask(UsbDevice device) {
            mDevice = device;
        }

        @Override
        public void run() {

            Exception result = doInBackground(mDevice);
            getProxyActivity().runOnUiThread(() -> onPostExecute(result));
        }

        private Exception doInBackground(UsbDevice... params) {

            Exception result = null;

            try {

                mReader.open(params[0]);

            } catch (Exception e) {

                result = e;
            }

            return result;
        }

        private void onPostExecute(Exception result) {

            if (result != null) {

            } else {

                int numSlots = mReader.getNumSlots();
                int actionNum = Reader.CARD_WARM_RESET;
                if (numSlots != Spinner.INVALID_POSITION
                        && actionNum != Spinner.INVALID_POSITION) {

                    if (actionNum < Reader.CARD_POWER_DOWN
                            || actionNum > Reader.CARD_WARM_RESET) {
                        actionNum = Reader.CARD_WARM_RESET;
                    }

                    // Set parameters
                    PowerParams params = new PowerParams();
                    params.slotNum = numSlots;
                    params.action = actionNum;

                    new Thread(new PowerTask(params)).start();
                }
            }
        }
    }

    private static class PowerResult {

        public byte[] atr;
        public Exception e;
    }

    private class PowerTask implements Runnable {

        private final PowerParams mParams;

        public PowerTask(PowerParams params) {
            mParams = params;
        }

        @Override
        public void run() {

            PowerResult result = doInBackground(mParams);
            getProxyActivity().runOnUiThread(() -> onPostExecute(result));
        }

        private PowerResult doInBackground(PowerParams... params) {

            PowerResult result = new PowerResult();

            try {

                result.atr = mReader.power(params[0].slotNum, params[0].action);

            } catch (Exception e) {

                result.e = e;
            }

            return result;
        }

        private void onPostExecute(PowerResult result) {

            if (result.e != null) {


            } else {
                SetProtocolParams params = new SetProtocolParams();
                params.slotNum = mParams.slotNum;
                params.preferredProtocols = Reader.PROTOCOL_UNDEFINED | Reader.PROTOCOL_T0 | Reader.PROTOCOL_T1;
                new Thread(new SetProtocolTask(params)).start();

            }
        }
    }

    private static class SetProtocolParams {

        public int slotNum;
        public int preferredProtocols;
    }

    private static class SetProtocolResult {

        public int activeProtocol;
        public Exception e;
    }

    private class SetProtocolTask implements Runnable {

        private final SetProtocolParams mParams;

        public SetProtocolTask(SetProtocolParams params) {
            mParams = params;
        }

        @Override
        public void run() {

            SetProtocolResult result = doInBackground(mParams);
            getProxyActivity().runOnUiThread(() -> onPostExecute(result));
        }

        private SetProtocolResult doInBackground(SetProtocolParams... params) {

            SetProtocolResult result = new SetProtocolResult();

            try {

                result.activeProtocol = mReader.setProtocol(params[0].slotNum,
                        params[0].preferredProtocols);

            } catch (Exception e) {

                result.e = e;
            }

            return result;
        }

        private void onPostExecute(SetProtocolResult result) {

            if (result.e != null) {


            } else {
                TransmitParams params = new TransmitParams();
                params.slotNum = mParams.slotNum;
                params.controlCode = -1;
                params.commandString = "FF 88 00 04 60 00h";

                // Transmit APDU
                new Thread(new TransmitTask(params)).start();

            }
        }
    }

    private static class TransmitParams {

        public int slotNum;
        public int controlCode;
        public String commandString;
    }

    private static class TransmitProgress {

        public int controlCode;
        public byte[] command;
        public int commandLength;
        public byte[] response;
        public int responseLength;
        public Exception e;
    }

    private class TransmitTask implements Runnable {

        private final TransmitParams mParams;

        public TransmitTask(TransmitParams params) {
            mParams = params;
        }

        @Override
        public void run() {
            doInBackground(mParams);
        }

        private void doInBackground(TransmitParams... params) {

            TransmitProgress progress;

            byte[] command;
            byte[] response;
            int responseLength;
            int foundIndex;
            int startIndex = 0;

            do {

                // Find carriage return
                foundIndex = params[0].commandString.indexOf('\n', startIndex);
                if (foundIndex >= 0) {
                    command = Hex.toByteArray(params[0].commandString.substring(
                            startIndex, foundIndex));
                } else {
                    command = Hex.toByteArray(params[0].commandString
                            .substring(startIndex));
                }

                // Set next start index
                startIndex = foundIndex + 1;

                response = new byte[65538];
                progress = new TransmitProgress();
                progress.controlCode = params[0].controlCode;
                try {

                    if (params[0].controlCode < 0) {

                        // Transmit APDU
                        responseLength = mReader.transmit(params[0].slotNum,
                                command, command.length, response,
                                response.length);

                    } else {

                        // Transmit control command
                        responseLength = mReader.control(params[0].slotNum,
                                params[0].controlCode, command, command.length,
                                response, response.length);
                    }

                    progress.command = command;
                    progress.commandLength = command.length;
                    progress.response = response;
                    progress.responseLength = responseLength;
                    progress.e = null;

                } catch (Exception e) {

                    progress.command = null;
                    progress.commandLength = 0;
                    progress.response = null;
                    progress.responseLength = 0;
                    progress.e = e;
                }

                TransmitProgress finalProgress = progress;
                getProxyActivity().runOnUiThread(() -> onProgressUpdate(finalProgress));

            } while (foundIndex >= 0);
        }

        private void onProgressUpdate(TransmitProgress... progress) {

            if (progress[0].e != null) {

//                logMsg(progress[0].e.toString());

            } else {
                mBinding.tvOutInfo.setText(bufferToString(progress[0].response, progress[0].responseLength));

//                logMsg("Command:");
//                logBuffer(progress[0].command, progress[0].commandLength);
//
//                logMsg("Response:");
//                logBuffer(progress[0].response, progress[0].responseLength);
//
//                if (progress[0].response != null
//                        && progress[0].responseLength > 0) {
//
//                    int controlCode;
//                    int i;
//
//                    // Show control codes for IOCTL_GET_FEATURE_REQUEST
//                    if (progress[0].controlCode == Reader.IOCTL_GET_FEATURE_REQUEST) {
//
//                        mFeatures.fromByteArray(progress[0].response,
//                                progress[0].responseLength);
//
//                        logMsg("Features:");
//                        for (i = Features.FEATURE_VERIFY_PIN_START;
//                             i <= Features.FEATURE_CCID_ESC_COMMAND; i++) {
//
//                            controlCode = mFeatures.getControlCode(i);
//                            if (controlCode >= 0) {
//                                logMsg("Control Code: " + controlCode + " ("
//                                        + featureStrings[i] + ")");
//                            }
//                        }
//
//                        // Enable buttons if features are supported
//                        mVerifyPinButton
//                                .setEnabled(mFeatures
//                                        .getControlCode(Features.FEATURE_VERIFY_PIN_DIRECT) >= 0);
//                        mModifyPinButton
//                                .setEnabled(mFeatures
//                                        .getControlCode(Features.FEATURE_MODIFY_PIN_DIRECT) >= 0);
//                    }
//
//                    controlCode = mFeatures
//                            .getControlCode(Features.FEATURE_IFD_PIN_PROPERTIES);
//                    if (controlCode >= 0
//                            && progress[0].controlCode == controlCode) {
//
//                        PinProperties pinProperties = new PinProperties(
//                                progress[0].response,
//                                progress[0].responseLength);
//
//                        logMsg("PIN Properties:");
//                        logMsg("LCD Layout: "
//                                + Hex.toHexString(pinProperties.getLcdLayout()));
//                        logMsg("Entry Validation Condition: "
//                                + Hex.toHexString(pinProperties
//                                .getEntryValidationCondition()));
//                        logMsg("Timeout 2: "
//                                + Hex.toHexString(pinProperties.getTimeOut2()));
//                    }
//
//                    controlCode = mFeatures
//                            .getControlCode(Features.FEATURE_GET_TLV_PROPERTIES);
//                    if (controlCode >= 0
//                            && progress[0].controlCode == controlCode) {
//
//                        TlvProperties readerProperties = new TlvProperties(
//                                progress[0].response,
//                                progress[0].responseLength);
//
//                        Object property;
//                        logMsg("TLV Properties:");
//                        for (i = TlvProperties.PROPERTY_wLcdLayout;
//                             i <= TlvProperties.PROPERTY_wIdProduct; i++) {
//
//                            property = readerProperties.getProperty(i);
//                            if (property instanceof Integer) {
//                                logMsg(propertyStrings[i] + ": "
//                                        + Hex.toHexString((Integer) property));
//                            } else if (property instanceof String) {
//                                logMsg(propertyStrings[i] + ": " + property);
//                            }
//                        }
//                    }
//                }
            }
        }
    }

    private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();

    private String bufferToString(byte[] buffer, int bufferLength) {

        StringBuilder builder = new StringBuilder(3 * bufferLength);

        for (int i = 0; i < bufferLength; i++) {

            int tmp = buffer[i] & 0xFF;

            if (i % 16 == 0) {
                if (builder.length() > 0) {

                    builder.setLength(0);
                }
            } else {
                builder.append(" ");
            }

            builder.append(HEX_DIGITS[tmp >>> 4]);
            builder.append(HEX_DIGITS[tmp & 0x0F]);
        }

        if (builder.length() > 0) {
            return builder.toString();
        } else {
            return "";
        }
    }
}
